/*
 * Copyright (C) 2007-2009 Alessandro Melandri
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package net.melandri.jtextfileparser.filters;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

import net.melandri.jtextfileparser.beans.Row;
import net.melandri.jtextfileparser.utils.StringUtils;

import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * Output filter class that generate an XML file using the TextFile content.
 * To use this filter you must set at least the path and the name of the new file.
 * Optionally you can set the character encoding, DTD reference, root element name, row element name, row numbering and field names
 * 
 * @author Alessandro Melandri
 * @since 1.4
 * */
public class XMLFileFilter extends OutputFileFilterImpl implements OutputFileFilter {

	private String characterEncoding = "ISO-8859-1";
	private String dtd = null;
	
	private String[] fieldsNames = null;
	private String rootElementName = "ROWS";
	private String rowElementName = "ROW";
	
	private boolean displayRowNumbers = true;
	
	public XMLFileFilter(){
		super();
	}
	
	/**
	 * Contains the code to write data to the file system. This method is used when you invoke the <code>write</code> method in a TextFile object.
	 * 
	 * @param rows ArrayList of Row objects. 
	 * */
	public void write(ArrayList rows){
		
		// Get the row subset, if any
		
		ArrayList filteredRows = getRowsSubset(rows);
		
		FileOutputStream outputStream = null;
		PrintWriter printWriter = null;
		
		try {
			
			// Inizialize document builders
			
			outputStream = new FileOutputStream(StringUtils.composeFilePath(this.filePath, this.fileName));
			printWriter = new PrintWriter(outputStream);
			StreamResult streamResult = new StreamResult(printWriter);
			SAXTransformerFactory transformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
			
			TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
			Transformer serializer = transformerHandler.getTransformer();
			
			// Setup document properties
			
			serializer.setOutputProperty(OutputKeys.ENCODING,characterEncoding);
			
			if (dtd != null)
				serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,dtd);
			
			serializer.setOutputProperty(OutputKeys.INDENT,"yes");
			
			transformerHandler.setResult(streamResult);
			
			// Begin!
			
			transformerHandler.startDocument();
			
			transformerHandler.startElement("", "", rootElementName, null);
			
			Iterator it = filteredRows.iterator();
			
			int rowCounter = 0;
			
			while (it.hasNext()){
				
				Row row = (Row)it.next();
				
				AttributesImpl atts = null;
				
				if (displayRowNumbers){
					
					atts = new AttributesImpl();
					atts.addAttribute("", "", "ID", "CDATA", ""+rowCounter);
					
				}
				
				transformerHandler.startElement("", "", rowElementName, atts);
				
				String[] thisFields = row.getFields();
				
				for (int i = 0; i < thisFields.length; i++) {
					
					String thisFieldName = "FIELD" + i;
					
					if (fieldsNames != null && i < fieldsNames.length && fieldsNames[i] !=null )
						thisFieldName = fieldsNames[i];
					
					String fieldValue = cleanField(thisFields[i]);
					
					transformerHandler.startElement("", "", thisFieldName, null);			
					transformerHandler.characters(fieldValue.toCharArray(), 0, fieldValue.length());
					transformerHandler.endElement("", "", thisFieldName);
				}
				
				transformerHandler.endElement("", "", rowElementName);
				
				rowCounter++;
			}
			
			// End. Close the document
			
			transformerHandler.endElement("", "", rootElementName);
			
			transformerHandler.endDocument();
			
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e){
			e.printStackTrace();
		} catch (SAXException e){
			e.printStackTrace();
		} finally{
			
			try {
				if (printWriter != null)
					printWriter.close();
				if (outputStream != null)
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	
	/* -------------------- Getters and Setters -------------------- */
	
	
	public String getCharacterEncoding() {
		return characterEncoding;
	}
	
	/**
	 * Sets the character encoding. If not set it will use "ISO-8859-1"
	 * */
	public void setCharacterEncoding(String characterEncoding) {
		this.characterEncoding = characterEncoding;
	}
	
	public String getDtd() {
		return dtd;
	}
	
	/**
	 * Sets the DTD URI
	 * */
	public void setDtd(String dtd) {
		this.dtd = dtd;
	}

	public String[] getFieldsNames() {
		return fieldsNames;
	}

	/**
	 * Sets elements names for the generated XML. You should specify a name for every field that will be included in every row element. 
	 * If the array doesn't contains names for all fields, for the missing field the filter will use a generic "FIELDn".
	 * If you dont set fields names, the filter will use FIEDLD1, FIELD2, FIELDn
	 * */
	public void setFieldsNames(String[] fieldsNames) {
		this.fieldsNames = fieldsNames;
	}

	public String getRootElementName() {
		return rootElementName;
	}

	/**
	 * Sets the name of the root element. Default is "ROWS"
	 * */
	public void setRootElementName(String rootElementName) {
		this.rootElementName = rootElementName;
	}

	/**
	 * Sets the name of the row element. Default is "ROW"
	 * */
	public String getRowElementName() {
		return rowElementName;
	}

	public void setRowElementName(String rowElementName) {
		this.rowElementName = rowElementName;
	}

	public boolean isDisplayRowNumbers() {
		return displayRowNumbers;
	}

	/**
	 * Sets if the row element must have an incremental ID attribute
	 * */
	public void setDisplayRowNumbers(boolean displayRowNumbers) {
		this.displayRowNumbers = displayRowNumbers;
	}
	
	
	
}
